package licode.unisop.single.user.infrastructure.gateway;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import licode.unisop.provider.api.SiUserProvider;
import licode.unisop.provider.info.*;
import licode.unisop.provider.utils.PluginUtil;
import licode.unisop.provider.vo.*;
import licode.unisop.single.user.application.entity.UserAccountEntity;
import licode.unisop.single.user.application.entity.UserAuthEntity;
import licode.unisop.single.user.application.entity.UserEntity;
import licode.unisop.single.user.application.gateway.*;
import licode.unisop.single.user.infrastructure.assembler.UserSingleConvert;
import licode.unisop.single.user.infrastructure.repository.dao.AccountCategory;
import licode.unisop.single.user.infrastructure.repository.mapper.UserMapper;
import licode.unisop.single.user.infrastructure.repository.po.RealmPlatGroupPO;
import licode.unisop.single.user.infrastructure.repository.po.UserPO;
import licode.unisop.types.AuthConst;
import licode.unisop.types.dp.AuthType;
import licode.unisop.types.util.IdGenUtil;
import licode.unisop.unilite.utils.EncryptUtil;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;

/**
 * 实现默认的领域用户接口
 *
 * @author licode
 */
@Primary
@Service
public class SiUserProviderSingle
        extends ServiceImpl<UserMapper, UserPO>
        implements SiUserProvider {
    @Resource
    private UserDao userDao;

    @Resource
    private UserAuthDao userAuthDao;

    @Resource
    private UserAccountDao userAccountDao;

    @Resource
    private RealmAssetDao realmAssetDao;

    @Resource
    private RealmPlatGroupDao realmPlatGroupDao;

    @Override
    public String findUserId(SiAccount account) {
        String userId = userAccountDao.findByAccount(account);
        return userId != null ? userId : "";
    }

    @Override
    public SiUser findUserByAccount(SiAccount account) {
        String userId = this.findUserId(account);
        if (null == userId || userId.isEmpty()) {
            return null;
        } else {
            return this.findUserById(UserIdInfo.builder()
                    .userId(userId)
                    .realmId(account.getRealmId())
                    .build());
        }
    }

    @Override
    public List<SiUser> queryUsers(SiQueryUserIn userIn) {
        if (null != userIn.getUserIds() && !userIn.getUserIds().isEmpty()) {
            return UserSingleConvert.toUserVO(super.lambdaQuery()
                    .eq(UserPO::getRealmId, userIn.getRealmId())
                    .in(UserPO::getUserId, userIn.getUserIds())
                    .eq(null != userIn.getState(), UserPO::getState, userIn.getState())
                    .list());
        } else {
            return null;
        }
    }

    @Override
    public SiUser findUserById(UserIdInfo idFind) {
        UserPO userInfoPo;
        SiUser userInfo = null;
        userInfoPo = super.lambdaQuery()
                .eq(UserPO::getUserId, idFind.getUserId())
                .eq(UserPO::getRealmId, idFind.getRealmId())
                .one();

        if (null != userInfoPo) {
            userInfo = UserSingleConvert.toUserVO(userInfoPo);
        }

        return userInfo;
    }

    @Override
    public SiUserGroup lookupUserGroup(UserGroupLookupIn req) {
        return realmAssetDao.lookupUserGroup(req);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 30)
    public void addUser(SiUser user) {
        UserPO userInfoPo;

        userInfoPo = UserSingleConvert.fromUserVO(user);
        userInfoPo.setUserId(user.getId());
        userInfoPo.setCreateTime(LocalDateTime.now());
        userInfoPo.setUpdateTime(LocalDateTime.now());

        super.save(userInfoPo);

        // 新增处理账号
        userAccountDao.add(this.toAccountEntity(user.getRealmId(), user.getId(), user.getAccounts()));

        // 插入默认密码验证（密码：12345678）
        UserAuthEntity authDef = new UserAuthEntity();

        authDef.setId(IdGenUtil.buildAuthInfoId());
        authDef.setUserId(userInfoPo.getUserId());
        authDef.setRealmId(authDef.getRealmId());
        authDef.setPassword(EncryptUtil.encryptMd5(AuthConst.DEF_PASSWORD));
        authDef.setFinger("");
        authDef.setSmsCode("");
        authDef.setEmailCode("");
        authDef.setUsbKey("");
        authDef.setProps("");
        authDef.setState(0);
        authDef.setUpdateTime(LocalDateTime.now());
        authDef.setCreateTime(authDef.getUpdateTime());

        userAuthDao.add(authDef);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 30)
    public void updateUser(SiUser user) {
        String strProps = null == user.getProps() ? null : JSON.toJSONString(user.getProps());
        super.lambdaUpdate()
                .set(null != user.getName(), UserPO::getName, user.getName())
                .set(null != user.getNickName(), UserPO::getNickName, user.getNickName())
                .set(null != user.getState(), UserPO::getState, user.getState())
                .set(null != user.getGroupId(), UserPO::getGroupId, user.getGroupId())
                .set(null != user.getPhone(), UserPO::getMobile, user.getPhone())
                .set(UserPO::getProps, strProps)
                .set(UserPO::getUpdateTime, LocalDateTime.now())
                .eq(UserPO::getUserId, user.getId())
                .eq(UserPO::getRealmId, user.getRealmId())
                .update();
    }

    @Transactional(rollbackFor = Throwable.class, timeout = 30)
    @Override
    public void batchAddUser(UserBatchInfo batchInfo) {
        List<UserPO> userList = new ArrayList<>();
        List<UserAccountEntity> accounts = new ArrayList<>();

        batchInfo.getUsers().forEach(user -> {
            UserPO userInfoPo = UserSingleConvert.fromUserVO(user);
            userInfoPo.setCreateTime(LocalDateTime.now());
            userInfoPo.setUpdateTime(LocalDateTime.now());
            userInfoPo.setUserId(UUID.fastUUID().toString());
            userList.add(userInfoPo);
            accounts.add(this.toAccountEntity(user.getRealmId(), userInfoPo.getUserId(), user.getAccounts()));
        });

        super.saveBatch(userList);
        userAccountDao.addBatch(accounts);
    }

    @Override
    public SiAccountSet findAccount(UserIdInfo idFind) {
        UserAccountEntity entity = userAccountDao.findAccount(idFind);
        return null == entity ? null : toAccountList(entity);
    }

    public boolean fillUserAccount(UserAccountEntity entity, SiAccount account) {
        String category = account.getCategory();
        String accVal = account.getAccount();

        if (AccountCategory.EMAIL.getCategory().equals(category)) {
            entity.setEmail(accVal);
        } else if (AccountCategory.MOBILE.getCategory().equals(category)) {
            entity.setMobile(accVal);
        } else if (AccountCategory.ACCOUNT.getCategory().equals(category)) {
            entity.setUserName(accVal);
        } else if (AccountCategory.WEI_XIN.getCategory().equals(category)) {
            entity.setWxUnionId(accVal);
        } else {
            return false;
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void saveAccountSet(SiAccountSet info) {
        UserAccountEntity entity = userAccountDao.findAccount(UserIdInfo
                .builder()
                .realmId(info.getRealmId())
                .userId(info.getUserId())
                .build());
        if (info.getData() != null && !info.getData().isEmpty()) {
            if (null != entity) {
                UserAccountEntity finalEntity = entity;
                info.getData().forEach(item -> fillUserAccount(finalEntity, item));
                userAccountDao.update(entity);
            } else {
                entity = toAccountEntity(info.getRealmId(), info.getUserId(), info.getData());
                userAccountDao.add(entity);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void saveAccount(SiAccountIn info) {
        UserAccountEntity entity = userAccountDao.findAccount(UserIdInfo
                .builder()
                .realmId(info.getRealmId())
                .userId(info.getUserId())
                .build());
        if (null != entity && null != info.getAccount()) {
            if (fillUserAccount(entity, info.getAccount())) {
                userAccountDao.update(entity);
            }
        }
    }

    @Override
    public String getName() {
        return "默认";
    }

    @Override
    public long getUserCount(SiRealmId realmId) {
        return super.lambdaQuery()
                .eq(UserPO::getRealmId, realmId.getId())
                .count();
    }

    @Override
    public SiPageResult<SiUser> findPage(UserPageInfo req) {
        Page<UserPO> page = new Page<>(req.getCurrent(), req.getPageSize());
        page = super.lambdaQuery()
                .eq(UserPO::getRealmId, req.getRealmId())
                .like(StrUtil.isNotBlank(req.getUserName()), UserPO::getName, req.getUserName())
                .like(StrUtil.isNotBlank(req.getNickName()), UserPO::getNickName, req.getNickName())
                .like(StrUtil.isNotBlank(req.getPhone()), UserPO::getMobile, req.getPhone())
                .eq(StrUtil.isNotBlank(req.getUserId()), UserPO::getUserId, req.getUserId())
                .eq(StrUtil.isNotBlank(req.getGroupId()), UserPO::getGroupId, req.getGroupId())
                .in((req.getIds() != null && !req.getIds().isEmpty()), UserPO::getUserId, req.getIds())
                .in((req.getGroups() != null && !req.getGroups().isEmpty()), UserPO::getGroupId, req.getGroups())
                .notIn(req.getFilterGroups() != null && !req.getFilterGroups().isEmpty(),
                        UserPO::getGroupId, req.getFilterGroups())
                .eq(null != req.getState(), UserPO::getState, req.getState())
                .ne(null != req.getNeState(), UserPO::getState, req.getNeState())
                .orderByDesc(UserPO::getLastLoginTime)
                .page(page);
        return convertPage(page, UserSingleConvert::toUserVO);
    }

    @Override
    public SiIdentitySet findIdentity(UserIdInfo idFind) {
        SiIdentitySet info = null;
        UserAuthEntity authInfo;
        SiUser user = this.findUserById(idFind);
        if (user != null) {
            authInfo = userAuthDao.findByUserId(idFind);
            if (null != authInfo) {
                info = toIdentityInfo(authInfo);
            }
        }

        return info;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void saveIdentitySet(SiIdentitySet info) {
        if (null != info && info.getData() != null) {
            UserAuthEntity authEntity = userAuthDao.findByUserId(UserIdInfo.builder()
                    .realmId(info.getRealmId())
                    .userId(info.getUserId()).build());

            if (null != authEntity) {
                fillIdentityInfo(authEntity, info);
                authEntity.setUpdateTime(LocalDateTime.now());
                userAuthDao.update(authEntity);
            } else {
                authEntity = new UserAuthEntity();
                authEntity.setId(IdGenUtil.buildAuthInfoId());
                authEntity.setUserId(info.getUserId());
                authEntity.setRealmId(info.getRealmId());

                authEntity.setUpdateTime(LocalDateTime.now());
                authEntity.setCreateTime(authEntity.getUpdateTime());

                fillIdentityInfo(authEntity, info);

                userAuthDao.add(authEntity);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void saveIdentity(SiIdentityIn info) {
        saveIdentitySet(SiIdentitySet.builder()
                .realmId(info.getRealmId())
                .userId(info.getUserId())
                .data(Collections.singletonList(info.getIdentity()))
                .build());
    }

    @Override
    public void updateLoginTime(SiUserLoginTimeIn loginTimeIn) {
        super.lambdaUpdate()
                .eq(UserPO::getRealmId, loginTimeIn.getRealmId())
                .eq(UserPO::getUserId, loginTimeIn.getUserId())
                .set(UserPO::getLastLoginTime, loginTimeIn.getLoginTime())
                .update();
    }

    @Override
    public void saveUserGroup(UserGroupSaveIn group) {
        realmAssetDao.saveUserGroup(group);
    }

    @Override
    public void assignGroupToUsers(SiGroupAssignToUsersIn req) {
        String groupId = req.getGroupId();
        req.setGroupId(null);
        List<UserEntity> users = userDao.findAssignUsers(req);
        if (null != users && !users.isEmpty()) {
            users.forEach(item -> item.setGroupId(groupId));
            userDao.updateBatch(users);
        }
    }

    @Override
    public List<SiUserGroup> findAssignGroup(UserGroupQueryIn req) {
        return realmPlatGroupDao.findAssignGroup(req);
    }

    @Override
    public SiPageResult<SiUserGroup> findUserGroupPage(UserGroupQueryPageIn req) {
        return realmAssetDao.findUserGroupPage(req);
    }

    @Override
    public SiPageResult<SiRealmPlatform> findRealPlatform(SiRealmAssignPlatPageIn req) {
        return realmPlatGroupDao.findRealPlatform(req);
    }

    @Override
    public List<SiRealmPlatform> findRealPlatformOther(SiRealmPlatformOtherIn req) {
        return realmPlatGroupDao.findRealPlatformOther(req);
    }

    @Override
    public String getRealmAssignToPlatform(String platformId) {
        RealmPlatGroupPO result;
        result = realmPlatGroupDao.findFirstGroupOfPlatform(platformId);
        return null == result ? "" : result.getRealmId();
    }

    @Override
    public void clearGroupsOfPlatform(SiRealmGroupsOfPlatClearIn req) {
        if (StrUtil.isNotBlank(req.getPlatformId())) {
            realmPlatGroupDao.clearGroupsOfPlatform(req.getPlatformId());
        }
    }

    @Override
    public void saveGroupsOfPlatform(SiUserGroupsAssignToPlatIn req) {
        if (null != req.getGroups() && !req.getGroups().isEmpty()) {
            realmPlatGroupDao.assignGroupToPlatform(req);
        }
    }

    @Override
    public void assignGroupsToPlatform(SiUserGroupsAssignToPlatIn req) {
        if (null != req.getGroups() && !req.getGroups().isEmpty()) {
            realmPlatGroupDao.removeGroupFromPlatform(UserGroupRemoveFromPlatIn.builder()
                    .realmId(req.getRealmId())
                    .platformId(req.getPlatformId())
                    .groups(req.getGroups())
                    .build());
            realmPlatGroupDao.assignGroupToPlatform(req);
        }
    }

    @Override
    public void assignPlatsToUserGroup(SiPlatsAssignToUserGroupIn req) {
        if (null != req.getPlatIds() && !req.getPlatIds().isEmpty()) {
            realmPlatGroupDao.removePlatsFromGroup(req);
            realmPlatGroupDao.assignPlatsToUserGroup(req);
        }
    }

    @Override
    public long removeGroupFromPlatform(UserGroupRemoveFromPlatIn req) {
        realmPlatGroupDao.removeGroupFromPlatform(req);
        return realmPlatGroupDao.getGroupCountToPlatform(req);
    }

    @Override
    public void removeUsersFromGroup(UsersRemoveFromGroupIn req) {
        List<UserEntity> users = userDao.findAssignUsers(SiGroupAssignToUsersIn.builder()
                .realmId(req.getRealmId())
                .userIds(req.getUsers())
                .groupId(req.getGroupId())
                .build());
        if (null != users && !users.isEmpty()) {
            users.forEach(item -> item.setGroupId("default"));
            userDao.updateBatch(users);
        }
    }

    public static <K, T> SiPageResult<K> convertPage(IPage<T> page, Function<List<T>, List<K>> call) {
        SiPageResult<K> pageData = new SiPageResult<>();
        if (page != null) {
            pageData.setCurrent(page.getCurrent());
            pageData.setPageSize(page.getSize());
            pageData.setTotal(page.getTotal());
            pageData.setCount(page.getPages());
            pageData.setData(call.apply(page.getRecords()));
        }
        return pageData;
    }

    private SiAccountSet toAccountList(UserAccountEntity entity) {
        SiAccountSet accountInfo = new SiAccountSet();

        accountInfo.setRealmId(entity.getRealmId());
        accountInfo.setUserId(entity.getUserId());
        accountInfo.setData(new ArrayList<>());

        if (StrUtil.isNotBlank(entity.getMobile())) {
            accountInfo.getData().add(SiAccount.builder()
                    .realmId(entity.getRealmId())
                    .category(AccountCategory.MOBILE.getCategory())
                    .account(entity.getMobile())
                    .data("")
                    .build());
        }

        if (StrUtil.isNotBlank(entity.getEmail())) {
            accountInfo.getData().add(SiAccount.builder()
                    .realmId(entity.getRealmId())
                    .category(AccountCategory.EMAIL.getCategory())
                    .account(entity.getEmail())
                    .data("")
                    .build());
        }

        if (StrUtil.isNotBlank(entity.getUserName())) {
            accountInfo.getData().add(SiAccount.builder()
                    .realmId(entity.getRealmId())
                    .category(AccountCategory.ACCOUNT.getCategory())
                    .account(entity.getUserName())
                    .data("")
                    .build());
        }

        if (StrUtil.isNotBlank(entity.getWxUnionId())) {
            accountInfo.getData().add(SiAccount.builder()
                    .realmId(entity.getRealmId())
                    .category(AccountCategory.WEI_XIN.getCategory())
                    .account(entity.getWxUnionId())
                    .data("")
                    .build());
        }

        return accountInfo;
    }

    private UserAccountEntity toAccountEntity(String realmId, String userId, List<SiAccount> accounts) {
        UserAccountEntity entity = new UserAccountEntity();

        entity.setId(IdGenUtil.buildAccountId());
        entity.setRealmId(realmId);
        entity.setUserId(userId);

        accounts.forEach(item -> {
            AccountCategory category = AccountCategory.build(item.getCategory());
            if (category.isMobile()) {
                entity.setMobile(item.getAccount());
            }
            if (category.isEmail()) {
                entity.setEmail(item.getAccount());
            }
            if (category.isAccount()) {
                entity.setUserName(item.getAccount());
            }
            if (category.isWx()) {
                entity.setWxUnionId(item.getAccount());
            }
        });

        if (null == entity.getMobile()) {
            entity.setMobile("");
        }
        if (null == entity.getEmail()) {
            entity.setEmail("");
        }
        if (null == entity.getUserName()) {
            entity.setUserName("");
        }
        if (null == entity.getWxUnionId()) {
            entity.setWxUnionId("");
        }
        if (null == entity.getC01()) {
            entity.setC01("");
        }
        if (null == entity.getA01()) {
            entity.setA01("");
        }
        if (null == entity.getC02()) {
            entity.setC02("");
        }
        if (null == entity.getA02()) {
            entity.setA02("");
        }
        if (null == entity.getC03()) {
            entity.setC03("");
        }
        if (null == entity.getA03()) {
            entity.setA03("");
        }

        if (null == entity.getProps()) {
            entity.setProps("");
        }
        if (null == entity.getState()) {
            entity.setState(0);
        }

        entity.setUpdateTime(LocalDateTime.now());
        entity.setCreateTime(entity.getUpdateTime());

        return entity;
    }

    private SiIdentitySet toIdentityInfo(UserAuthEntity authInfo) {
        SiIdentitySet info = new SiIdentitySet();
        info.setRealmId(authInfo.getRealmId());
        info.setUserId(authInfo.getUserId());
        info.setData(new ArrayList<>());

        if (PluginUtil.isNotBlank(authInfo.getPassword())) {
            info.getData().add(SiIdentity.builder()
                    .authType(AuthType.PASSWORD.getType())
                    .authCode(authInfo.getPassword())
                    .authData("")
                    .build());
        }

        if (PluginUtil.isNotBlank(authInfo.getFace())) {
            info.getData().add(SiIdentity.builder()
                    .authType(AuthType.FACE.getType())
                    .authCode(authInfo.getFace())
                    .authData("")
                    .build());
        }

        if (PluginUtil.isNotBlank(authInfo.getFinger())) {
            info.getData().add(SiIdentity.builder()
                    .authType(AuthType.SMS_CODE.getType())
                    .authCode(authInfo.getFinger())
                    .authData("")
                    .build());
        }

        if (PluginUtil.isNotBlank(authInfo.getEmailCode())) {
            info.getData().add(SiIdentity.builder()
                    .authType(AuthType.EMAIL.getType())
                    .authCode(authInfo.getEmailCode())
                    .authData("")
                    .build());
        }

        return info;
    }

    private void fillIdentityInfo(UserAuthEntity authInfo, SiIdentitySet info) {
        info.getData().forEach(item -> {
            if (AuthType.PASSWORD.getType().equals(item.getAuthType())) {
                authInfo.setPassword(item.getAuthCode());
            }
            if (AuthType.FACE.getType().equals(item.getAuthType())) {
                authInfo.setFace(item.getAuthCode());
            }
            if (AuthType.SMS_CODE.getType().equals(item.getAuthType())) {
                authInfo.setSmsCode(item.getAuthCode());
            }
            if (AuthType.EMAIL.getType().equals(item.getAuthType())) {
                authInfo.setEmailCode(item.getAuthCode());
            }
        });
    }
}
